//
// DotNetNuke - http://www.dotnetnuke.com
// Copyright (c) 2002-2010
// by Perpetual Motion Interactive Systems Inc. ( http://www.perpetualmotion.ca )
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions 
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Text.RegularExpressions;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Host;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Tabs;
using DotNetNuke.Security.Permissions;
using DotNetNuke.Services.Exceptions;
namespace DotNetNuke.Services.Search
{
	public class SearchDataStore : SearchDataStoreProvider
	{
		private void AddIndexWords(int indexId, SearchItemInfo searchItem, string language)
		{
			SearchConfig settings = new SearchConfig(SearchDataStoreController.GetSearchSettings(searchItem.ModuleId));
			Dictionary<string, int> IndexWords = new Dictionary<string, int>();
			Dictionary<string, List<int>> IndexPositions = new Dictionary<string, List<int>>();
			string Content = GetSearchContent(searchItem);

		    string title = HtmlUtils.StripPunctuation(searchItem.Title, true);

            // Tab and Module Metadata
            // Retreive module and page names
		    ModuleInfo objModule = new ModuleController().GetModule(searchItem.ModuleId);
		    TabInfo objTab = new TabController().GetTab(objModule.TabID, objModule.PortalID, false);
		    string tabName = HtmlUtils.StripPunctuation(objTab.TabName, true);
		    string tabTitle = HtmlUtils.StripPunctuation(objTab.Title, true);
		    string tabDescription = HtmlUtils.StripPunctuation(objTab.Description, true);
		    string tabKeywords = HtmlUtils.StripPunctuation(objTab.KeyWords, true);
		    string tagfilter = PortalController.GetPortalSetting("SearchIncludedTagInfoFilter", objModule.PortalID,
		                                                         Host.SearchIncludedTagInfoFilter);

            // clean content
		    Content = HtmlUtils.CleanWithTagInfo(Content, tagfilter, true);
            // append tab and module metadata
		    Content = Content.ToLower() + title.ToLower() + " " + tabName.ToLower() + " " + tabTitle.ToLower() + " " +
		              tabDescription.ToLower() + " " + tabKeywords.ToLower();

			string[] ContentWords = Content.Split(' ');
			int intWord = 0;
			foreach (string strWord in ContentWords) {
				if (CanIndexWord(strWord, language, settings)) {
					intWord = intWord + 1;
					if (IndexWords.ContainsKey(strWord) == false) {
						IndexWords.Add(strWord, 0);
						IndexPositions.Add(strWord, new List<int>());
					}
					IndexWords[strWord] = IndexWords[strWord] + 1;
					IndexPositions[strWord].Add(intWord);
				}
			}
			Hashtable Words = GetSearchWords();
			int WordId;
			foreach (object objWord in IndexWords.Keys) {
				string strWord = Convert.ToString(objWord);
				if (Words.ContainsKey(strWord)) {
					WordId = Convert.ToInt32(Words[strWord]);
				} else {
					WordId = Data.DataProvider.Instance().AddSearchWord(strWord);
					Words.Add(strWord, WordId);
				}
				int SearchItemWordID = Data.DataProvider.Instance().AddSearchItemWord(indexId, WordId, IndexWords[strWord]);
				string strPositions = Null.NullString;
				foreach (int position in IndexPositions[strWord]) {
					strPositions += position.ToString() + ",";
				}
				Data.DataProvider.Instance().AddSearchItemWordPosition(SearchItemWordID, strPositions);
			}
		}
		private bool CanIndexWord(string strWord, string Locale, SearchConfig settings)
		{
			bool retValue = true;
			Hashtable CommonWords = GetCommonWords(Locale);
			if (Regex.IsMatch(strWord,"^\\d+$")) {
				if (!settings.SearchIncludeNumeric) {
					retValue = false;
				}
			} else {
				if (strWord.Length < settings.SearchMinWordlLength || strWord.Length > settings.SearchMaxWordlLength) {
					retValue = false;
				} else {
					if (CommonWords.ContainsKey(strWord) == true && !settings.SearchIncludeCommon) {
						retValue = false;
					}
				}
			}
			return retValue;
		}
		private Hashtable GetCommonWords(string Locale)
		{
			string strCacheKey = "CommonWords" + Locale;
			Hashtable objWords = (Hashtable)DataCache.GetCache(strCacheKey);
			if (objWords == null) {
				objWords = new Hashtable();
				IDataReader drWords = Data.DataProvider.Instance().GetSearchCommonWordsByLocale(Locale);
				try {
					while (drWords.Read()) {
						objWords.Add(drWords["CommonWord"].ToString(), drWords["CommonWord"].ToString());
					}
				} finally {
					drWords.Close();
					drWords.Dispose();
				}
				DataCache.SetCache(strCacheKey, objWords);
			}
			return objWords;
		}
		private Hashtable GetSearchWords()
		{
			string strCacheKey = "SearchWords";
			Hashtable objWords = (Hashtable)DataCache.GetCache(strCacheKey);
			if (objWords == null) {
				objWords = new Hashtable();
				IDataReader drWords = Data.DataProvider.Instance().GetSearchWords();
				try {
					while (drWords.Read()) {
						objWords.Add(drWords["Word"].ToString(), drWords["SearchWordsID"]);
					}
				} finally {
					drWords.Close();
					drWords.Dispose();
				}
				DataCache.SetCache(strCacheKey, objWords, TimeSpan.FromMinutes(2));
			}
			return objWords;
		}
		protected virtual string GetSearchContent(SearchItemInfo SearchItem)
		{
			return SearchItem.Content;
		}
		public override SearchResultsInfoCollection GetSearchItems(int PortalID, int TabID, int ModuleID)
		{
			return SearchDataStoreController.GetSearchResults(PortalID, TabID, ModuleID);
		}
		public override SearchResultsInfoCollection GetSearchResults(int portalID, string criteria)
		{
			SearchResultsInfo searchResult;
			bool hasExcluded = Null.NullBoolean;
			bool hasMandatory = Null.NullBoolean;
			PortalController objPortalController = new PortalController();
			PortalInfo objPortal = objPortalController.GetPortal(portalID);
			PortalSettings _PortalSettings = new PortalSettings(objPortal);
			Hashtable commonWords = GetCommonWords(_PortalSettings.DefaultLanguage);
			criteria = criteria.ToLower();
			SearchCriteriaCollection searchWords = new SearchCriteriaCollection(criteria);
			Dictionary<string, SearchResultsInfoCollection> searchResults = new Dictionary<string, SearchResultsInfoCollection>();
			Dictionary<int, Dictionary<int, SearchResultsInfo>> dicResults = new Dictionary<int, Dictionary<int, SearchResultsInfo>>();
			foreach (SearchCriteria criterion in searchWords) {
				if (commonWords.ContainsKey(criterion.Criteria) == false || _PortalSettings.SearchIncludeCommon) {
					if (!searchResults.ContainsKey(criterion.Criteria)) {
						searchResults.Add(criterion.Criteria, SearchDataStoreController.GetSearchResults(portalID, criterion.Criteria));
					}
					if (searchResults.ContainsKey(criterion.Criteria)) {
						foreach (SearchResultsInfo result in searchResults[criterion.Criteria]) {
							if (!criterion.MustExclude) {
								if (dicResults.ContainsKey(result.SearchItemID)) {
									Dictionary<int, SearchResultsInfo> dic = dicResults[result.SearchItemID];
									if (dic.ContainsKey(result.TabId)) {
										searchResult = dic[result.TabId];
										searchResult.Relevance += result.Relevance;
									} else {
										dic.Add(result.TabId, result);
									}
								} else {
									Dictionary<int, SearchResultsInfo> dic = new Dictionary<int, SearchResultsInfo>();
									dic.Add(result.TabId, result);
									dicResults.Add(result.SearchItemID, dic);
								}
							}
						}
					}
				}
			}
			foreach (SearchCriteria criterion in searchWords) {
				Dictionary<int, bool> mandatoryResults = new Dictionary<int, bool>();
				Dictionary<int, bool> excludedResults = new Dictionary<int, bool>();
				if (searchResults.ContainsKey(criterion.Criteria)) {
					foreach (SearchResultsInfo result in searchResults[criterion.Criteria]) {
						if (criterion.MustInclude) {
							mandatoryResults[result.SearchItemID] = true;
							hasMandatory = true;
						} else if (criterion.MustExclude) {
							excludedResults[result.SearchItemID] = true;
							hasExcluded = true;
						}
					}
				}
				foreach (KeyValuePair<int, Dictionary<int, SearchResultsInfo>> kvpResults in dicResults) {
					if (hasMandatory && (!mandatoryResults.ContainsKey(kvpResults.Key))) {
						foreach (SearchResultsInfo result in kvpResults.Value.Values) {
							result.Delete = true;
						}
					} else if (hasExcluded && (excludedResults.ContainsKey(kvpResults.Key))) {
						foreach (SearchResultsInfo result in kvpResults.Value.Values) {
							result.Delete = true;
						}
					}
				}
			}
			SearchResultsInfoCollection results = new SearchResultsInfoCollection();
			TabController objTabController = new TabController();
			Dictionary<int, Dictionary<int, bool>> dicTabsAllowed = new Dictionary<int, Dictionary<int, bool>>();
			foreach (KeyValuePair<int, Dictionary<int, SearchResultsInfo>> kvpResults in dicResults) {
				foreach (SearchResultsInfo result in kvpResults.Value.Values)
				{
					if (!result.Delete)
					{
						TabInfo objTab = objTabController.GetTab(result.TabId, portalID, false);
						if (TabPermissionController.CanViewPage(objTab)) {
							ModuleInfo objModule = new ModuleController().GetModule(result.ModuleId, result.TabId, false);
							if (ModulePermissionController.CanViewModule(objModule)) {
								results.Add(result);
							}
						}
					}
				}
			}
			return results;
		}
		public override void StoreSearchItems(SearchItemInfoCollection SearchItems)
		{
			Dictionary<int, string> Modules = new Dictionary<int, string>();
			foreach (SearchItemInfo item in SearchItems) {
				if (!Modules.ContainsKey(item.ModuleId)) {
					Modules.Add(item.ModuleId, "en-US");
				}
			}

		    TabController objTabs = new TabController();
		    ModuleInfo objModule = new ModuleInfo();
		    TabInfo objTab = new TabInfo();

			SearchItemInfo searchItem;
			Dictionary<string, SearchItemInfo> indexedItems;
			SearchItemInfoCollection moduleItems;
			foreach (KeyValuePair<int, string> kvp in Modules) {
				indexedItems = SearchDataStoreController.GetSearchItems(kvp.Key);
				moduleItems = SearchItems.ModuleItems(kvp.Key);
				for (int iSearch = moduleItems.Count - 1; iSearch >= 0; iSearch += -1) {
					searchItem = moduleItems[iSearch];
					SearchItemInfo indexedItem = null;
					if (indexedItems.TryGetValue(searchItem.SearchKey, out indexedItem)) {
                        //Get the tab where the search item resides -- used in date comparison
					    objModule = new ModuleController().GetModule(searchItem.ModuleId);
					    objTab = objTabs.GetTab(searchItem.TabId, objModule.PortalID, false);

						if (indexedItem.PubDate < searchItem.PubDate
                            || indexedItem.PubDate < objModule.LastModifiedOnDate 
                            || indexedItem.PubDate < objTab.LastModifiedOnDate) {
							try {
                                if(searchItem.PubDate < objModule.LastModifiedOnDate)
                                    searchItem.PubDate = objModule.LastModifiedOnDate;
                                if(searchItem.PubDate < objTab.LastModifiedOnDate)
                                    searchItem.PubDate = objTab.LastModifiedOnDate;

								searchItem.SearchItemId = indexedItem.SearchItemId;
								SearchDataStoreController.UpdateSearchItem(searchItem);
								SearchDataStoreController.DeleteSearchItemWords(searchItem.SearchItemId);
								AddIndexWords(searchItem.SearchItemId, searchItem, kvp.Value);
							} catch (Exception ex) {
								DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
							}
						}
						indexedItems.Remove(searchItem.SearchKey);
						SearchItems.Remove(searchItem);
					} else {
						try {
							int indexID = SearchDataStoreController.AddSearchItem(searchItem);
							AddIndexWords(indexID, searchItem, kvp.Value);
						} catch (Exception ex) {
							Services.Exceptions.Exceptions.LogSearchException(new SearchException(ex.Message, ex, searchItem));
						}
					}
				}
			}
		}
	}
}
